<?php

    #-------------------------------------------------------------#
	# This file is part of the Tekuna MVC Microframework for PHP. #
	# (c) 2009 Niels Jäckel <niels.jaeckel@tekuna.org>            #
	# Please visit http://tekuna.org/ for more information.       #
	#                                                             #
	# This software is licensed under the MIT License. Please     #
	# view the LICENSE file for the full text of this license.    #
    #-------------------------------------------------------------#


	Tekuna :: load('org.tekuna.core.application.TekunaApplicationException');
	Tekuna :: load('org.tekuna.core.config.ApplicationConfiguration');
	Tekuna :: load('org.tekuna.core.config.ApplicationModule');
	Tekuna :: load('org.tekuna.core.config.ApplicationComponent');
	Tekuna :: load('org.tekuna.core.config.ApplicationComponentRoute');
	Tekuna :: load('org.tekuna.core.config.ApplicationXMLParseException');
	Tekuna :: load('org.tekuna.core.config.InvalidConfigurationException');


	/**
	 * This builder class builds a configuration object tree out by interpreting
	 * several file formats. By default the XML format is supported.
	 */

	class ApplicationConfigurationBuilder {


		/**
		 * Build a configuration object tree by loading the data from an XML file.
		 * This method does some integrity checks and gives wordy error messages.
		 * The messages are notified as ApplicationXMLParseExceptions.
		 *
		 * The simplexml extension must be available. A TekunaApplicationException
		 * is thrown in case of an error. If the XML configuration file does not exist,
		 * a TekunaApplicationException is thrown.
		 *
		 * @param string $sFileName the file name of the XML configuration file
		 * @return ApplicationConfiguration returns an object representation of the configuration file
		 */

		public static function buildConfigurationFromXMLFile($sFileName) {

			// check the existence of the extension
			if (! extension_loaded('simplexml')) {

				throw new TekunaApplicationException("The simplexml extension must be available to load the Tekuna application configuration file '$sFileName'.");
			}

			// check file existence
			if (! file_exists($sFileName) || ! is_readable($sFileName)) {

				throw new TekunaApplicationException("Application Configuration file '$sFileName' does not exist or is not readable.");
			}


			try {

				// init the configuration object
				$objConfig = new ApplicationConfiguration();

				// read xml file
				$objXML = simplexml_load_file($sFileName);

				// load application attributes
				$arrAppAttributes = $objXML -> attributes();
				if (! isset($arrAppAttributes['languages'])) {

					throw new ApplicationXMLParseException("No languages defined for application in file '$sFileName'.");
				}

				// parse the languages
				$arrLanguages = explode(',', $arrAppAttributes['languages']);

				if (! is_array($arrLanguages) || count($arrLanguages) < 1) {

					throw new ApplicationXMLParseException("No languages defined for application in file '$sFileName'.");
				}

				// check all languages
				foreach ($arrLanguages as $sLang) {

					if (trim($sLang) == '') {

						throw new ApplicationXMLParseException("Invalid language defined in file '$sFileName'.");
					}
				}

				$objConfig -> setLanguages($arrLanguages);

				// find all module declarations
				$arrModuleDeclarations = $objXML -> xpath('module');

				// at least one module must be defined
				if (! is_array($arrModuleDeclarations) || count($arrModuleDeclarations) < 1) {

					throw new ApplicationXMLParseException("No module declarations found in file '$sFileName'.");
				}

				// parse data into object tree
				foreach ($arrModuleDeclarations as $i => $objModuleXML) {

					// for better human understanding...
					$i++;

					// get attributes
					$arrModuleAttr = $objModuleXML -> attributes();

					// base URL must be given
					if (! isset($arrModuleAttr['baseURL'])) {

						throw new ApplicationXMLParseException("No baseURL-attribute in module declaration #$i of file '$sFileName'.");
					}

					// layout must be given
					if (! isset($arrModuleAttr['layout'])) {

						throw new ApplicationXMLParseException("No layout-attribute in module declaration #$i of file '$sFileName'.");
					}

					// create module object
					$objModule = new ApplicationModule();
					$objModule -> setBaseURL((string) $arrModuleAttr['baseURL']);
					$objModule -> setLayout((string) $arrModuleAttr['layout']);

					// store HTTP status if given
					if (isset($arrModuleAttr['status'])) {

						$objModule -> setStatus((integer) $arrModuleAttr['status']);
					}

					// find all component declarations
					$arrComponentDeclarations = $objModuleXML -> xpath('component');

					// at least one component must be defined
					if (! is_array($arrComponentDeclarations) || count($arrComponentDeclarations) < 1) {

						throw new ApplicationXMLParseException("No component declarations found in module #$i in file '$sFileName'.");
					}

					// parse all components
					foreach ($arrComponentDeclarations as $j => $objComponentXML) {

						// again ... human readability
						$j++;

						// get attributes
						$arrComponentAttr = $objComponentXML -> attributes();

						// check component definition
						if (! isset($arrComponentAttr['slot'])) {

							throw new ApplicationXMLParseException("No slot-attribute in component declaration #$j in module declaration #$i of file '$sFileName'.");
						}

						// try to find routes
						$arrRoutes = $objComponentXML -> xpath('route');

						// either fragment id or routes must be defined
						$bFragment = isset($arrComponentAttr['fragment']);
						$bController = isset($arrComponentAttr['controller']);
						$bRoutes = (is_array($arrRoutes) && count($arrRoutes) > 0);

						// trigger error if not only one of the possible attributes or none is given
						if (intval($bFragment) + intval($bController) + intval($bRoutes) != 1) {

							throw new ApplicationXMLParseException("Either fragment-attribute, controller-attribute or some routes must be given in component declaration #$j in module declaration #$i of file '$sFileName'.");
						}

						// create component object
						$objComponent = new ApplicationComponent();
						$objComponent -> setSlot((string) $arrComponentAttr['slot']);

						// set fragment
						if ($bFragment) {

							$objComponent -> setFragment((string) $arrComponentAttr['fragment']);
						}

						// set the controller
						if ($bController) {

							$objComponent -> setController((string) $arrComponentAttr['controller']);
						}

						// store HTTP status if given
						if (isset($arrComponentAttr['status'])) {

							$objComponent -> setStatus((integer) $arrComponentAttr['status']);
						}

						// process routes
						if ($bRoutes) {

							// iterate all route elements
							foreach ($arrRoutes as $k => $objRoutesXML) {

								// and the last readability increasing
								$k++;

								// get attributes
								$arrRouteAttr = $objRoutesXML -> attributes();

								// init route object
								$objRoute = new ApplicationComponentRoute();

								// check route definition
								if (! isset($arrRouteAttr['pattern'])) {

									throw new ApplicationXMLParseException("No pattern-attribute in route #$k in component declaration #$j in module declaration #$i of file '$sFileName'.");
								}

								// set pattern
								$objRoute -> setPattern((string) $arrRouteAttr['pattern']);

								// store status if given
								if (isset($arrRouteAttr['status'])) {

									$objRoute -> setStatus((integer) $arrRouteAttr['status']);
								}

								// check fragment or controller
								$bRoutedFragment = isset($arrRouteAttr['fragment']);
								$bRoutedController = isset($arrRouteAttr['controller']);

								// trigger error if more or less than exactly one attribute are given
								if (intval($bRoutedFragment) + intval($bRoutedController) != 1) {

									throw new ApplicationXMLParseException("Either controller-attribute or fragment-attribute must be given in route #$k in component declaration #$j in module declaration #$i of file '$sFileName'.");
								}

								// add the route to the component
								if ($bRoutedController) {

									$objRoute -> setController((string) $arrRouteAttr['controller']);
								}
								else{

									$objRoute -> setFragment((string) $arrRouteAttr['fragment']);
								}

								// store route for component
								$objComponent -> addRoute($objRoute);
							}
						}

						// append to parent module
						$objModule -> addComponent($objComponent);
					}


					// store this new module
					$objConfig -> addModule($objModule);
				}
			}
			catch (InvalidConfigurationException $e) {

				throw new ApplicationXMLParseException("Error while parsing application configuration '$sFileName'.", $e);
			}

			// check the validation of the whole config object tree
			if (! $objConfig -> isValid()) {

				throw new ApplicationXMLParseException("Error while parsing application configuration '$sFileName'.", $e);
			}

			return $objConfig;
		}
	}

